#include <errno.h>
#include <fcntl.h>
#include <pty.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <termios.h>
#include <unistd.h>

#define DEBUG_LOG(msg, ...) fprintf(stderr, "[DEBUG] " msg "\n", ##__VA_ARGS__)

#define MAX_BUFFER_SIZE 1024

#ifndef VDISABLE
#ifdef _POSIX_VDISABLE
#define VDISABLE _POSIX_VDISABLE
#else
#define VDISABLE 0377
#endif
#endif

// 程序名称
char* self;
// 用户和后台进程进行通信的命名管道
int pipe_fd;
// 用户启动初始化脚本描述符
int init_file_fd;
// 输出日志文件描述符
int output_file_fd;
// 一对终端设备
int master_fd, slave_fd;
// 命令中的需要创建的命名管道名
char* input_file;
// 程序是否被终止
int is_stop;
// 子进程 PId
pid_t child_pid;

// 处理管道输入和程序输出日志
void
read_and_send_input(const char* input_file, const char* init_file, int master_fd, int delay);

// 程序退出时的清理工作
void
clean();

// 使用说明
void
usage();

// 处理终止信号
void
handle_terminal_signal(int signum);

int
main(int argc, char** argv)
{
    // 注册信号处理函数
    signal(SIGINT, handle_terminal_signal);
    signal(SIGTERM, handle_terminal_signal);

    // 命令行参数解析
    self = argv[0];
    argv++, argc--;

    if (argc < 3) {
        usage();
        return EXIT_FAILURE;
    }

    // 初始化脚本
    const char* init_file = NULL;
    if (strcmp(argv[0], "--init") == 0) {
        init_file = argv[1];
        argc = argc - 2;
        argv = argv + 2;
    }

    // 执行脚本的延时, 默认 50 ms
    const char* delay_time_str = NULL;
    if (strcmp(argv[0], "--delay") == 0) {
        delay_time_str = argv[1];
        argc = argc - 2;
        argv = argv + 2;
    }
    int delay_time = 50;
    if (delay_time_str != NULL) {
        delay_time = atoi(delay_time_str);
    }

    if (argc < 3) {
        usage();
        return EXIT_FAILURE;
    }

    input_file = argv[0];
    const char* output_log = argv[1];
    char* const program = argv[2];

    int app_argc = argc - 2;
    if (app_argc < 0) {
        app_argc = 0;
    }

    char* app_args[app_argc + 1];
    app_args[0] = program;
    for (int i = 1; i < app_argc; i++) {
        app_args[i] = argv[i + 2];
    }
    app_args[app_argc] = NULL;

    // 原终端文件描述符以及终端属性
    int original_fd = STDIN_FILENO;
    struct termios original_attr, slave_attr;
    struct winsize ws;

    // 创建伪终端与程序交互
    if (openpty(&master_fd, &slave_fd, NULL, NULL, NULL) == -1) {
        perror("openpty failed");
        return EXIT_FAILURE;
    }

    // 设置伪终端属性
    tcgetattr(original_fd, &original_attr);
    slave_attr = original_attr;
    slave_attr.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL);
    slave_attr.c_iflag &= ~(IXON | IXOFF);
    slave_attr.c_oflag &= ~(OPOST);
    slave_attr.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
    slave_attr.c_cflag &= ~(CSIZE | PARENB);
    slave_attr.c_cflag |= CS8;
    slave_attr.c_cc[VLNEXT] = VDISABLE;
    slave_attr.c_cc[VMIN] = 1;
    slave_attr.c_cc[VTIME] = 0;
    tcsetattr(slave_fd, TCSANOW, &slave_attr);
    ws.ws_row = 25;
    ws.ws_col = 80;
    ioctl(slave_fd, TIOCSWINSZ, &ws);

    child_pid = fork();
    if (child_pid == -1) {
        perror("fork failed");
        clean();
        return EXIT_FAILURE;
    } else if (child_pid == 0) { // 子进程

        // 创建新的会话和进程组
        setsid();

        dup2(slave_fd, STDIN_FILENO);
        dup2(slave_fd, STDOUT_FILENO);
        dup2(slave_fd, STDERR_FILENO);

        // 关闭主从设备文件描述符
        close(master_fd);
        close(slave_fd);

        execvp(program, app_args);
        perror("execvp failed");
        exit(1);
    }

    // 父进程
    close(slave_fd);

    output_file_fd = open(output_log, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (output_file_fd == -1) {
        perror("Failed to open output file");
        clean();
        return EXIT_FAILURE;
    }

    dup2(output_file_fd, STDOUT_FILENO);
    dup2(output_file_fd, STDERR_FILENO);

    close(output_file_fd);

    read_and_send_input(input_file, init_file, master_fd, delay_time);

    close(master_fd);

    // 等待子进程退出
    wait(NULL);

    clean();

    return EXIT_SUCCESS;
}

void
usage()
{
    printf("Usage: %s [--init init.txt] input.txt output.log program [args...]\n", self);
}

void
read_and_send_input(const char* input_file, const char* init_file, int master_fd, int delay)
{
    // 创建输入用的命名管道
    if (mkfifo(input_file, 0666) == -1) {
        perror("create pipe failed");
        return;
    }

    // 打开命名管道
    pipe_fd = open(input_file, O_RDONLY | O_NONBLOCK);
    if (pipe_fd == -1) {
        perror("open pipe failed");
        return;
    }

    // 如果存在在初始化脚本，则执行初始化
    usleep(delay * 1000);
    if (init_file != NULL) {
        init_file_fd = open(init_file, O_RDONLY);
        if (init_file_fd == -1) {
            perror("open file failed");
            return;
        }
        char init_buffer[MAX_BUFFER_SIZE];
        ssize_t init_count;
        while ((init_count = read(init_file_fd, init_buffer, sizeof(init_buffer))) > 0) {
            write(master_fd, init_buffer, init_count);
            fflush(stdout);
        }
    }

    fd_set rfds;
    struct timeval tv;
    int result;

    // 设置超时时间
    tv.tv_sec = 1;
    tv.tv_usec = 0;

    is_stop = 1;
    int retry_count = 0;
    const int max_retries = 10;
    const int retry_delay_ms = 100;

    // 监听事件
    while (is_stop) {
        // 防止 cpu 占用过高
        usleep(50 * 1000);

        FD_ZERO(&rfds);
        FD_SET(master_fd, &rfds);
        FD_SET(pipe_fd, &rfds);

        result = select((pipe_fd > master_fd ? pipe_fd : master_fd) + 1, &rfds, NULL, NULL, &tv);

        if (result == -1) {
            // 如果是中断信号导致的异常，则不做处理等待退出
            if (errno == EINTR || errno == EBADF) {
                continue;
            }
            perror("select() failed");
            exit(EXIT_FAILURE);
        } else if (result == 0) {
            continue;
        }

        if (FD_ISSET(pipe_fd, &rfds)) {
            char buffer[MAX_BUFFER_SIZE];
            ssize_t count = read(pipe_fd, buffer, sizeof(buffer));
            if (count > 0) {
                write(master_fd, buffer, count);
                retry_count = 0;
            } else if (count == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
                // 延时然后重试
                if (retry_count >= max_retries) {
                    perror("read pipe failed");
                    break;
                }
                usleep(retry_delay_ms * 1000);
                retry_count++;
            } else if (count != 0) {
                perror("read pipe failed");
                break;
            }
        }

        if (FD_ISSET(master_fd, &rfds)) {
            char buffer[MAX_BUFFER_SIZE];
            ssize_t count = read(master_fd, buffer, sizeof(buffer));
            if (count > 0) {
                write(STDOUT_FILENO, buffer, count);
            } else if (count != 0) {
                if (errno == EIO) {
                    // 这里收到 IO 错误是因为为终端中的程序运行结束导致的，直接退出就好
                    break;
                }
                perror("read master_fd failed");
                break;
            }
        }
    }
}

void
handle_terminal_signal(int signum)
{
    // 标记停止等待退出
    is_stop = 0;

    clean();
}

void
clean()
{
    // 关闭打开的文件设备
    close(slave_fd);
    close(master_fd);
    close(pipe_fd);
    close(init_file_fd);
    close(output_file_fd);

    // 删除创建的管道文件
    if (access(input_file, F_OK) != -1) {
        if (unlink(input_file) == -1) {
            perror("delete pipe failed");
        }
    }

    // 结束启动的子进程
    kill(child_pid, SIGINT);
    int status;
    waitpid(child_pid, &status, 0);
}
